﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace Encloser.Configuration
{
    internal partial class ConfigForm<T> : Form where T : new()
    {
        public T Result = new T();

        private class Value
        {
            public FieldInfo field;
            public Control[] controls;
        }

        private readonly T config;
        private readonly List<Value> values = new List<Value>();
        private readonly Padding padLabel = new Padding(12, 12, 6, 12);
        private readonly Padding padTrackBarLabel = new Padding(3, 12, 6, 12);
        private readonly Padding padCommon = new Padding(3, 8, 3, 0);
        private readonly Padding padRadio = new Padding(3, 10, 3, 0);
        private readonly Size sizeSmallTextBox = new Size(50, 21);
        private readonly Size sizeMediumTextBox = new Size(100, 21);
        private readonly Size sizeLargeTextBox = new Size(200, 21);
        private readonly Size sizeTrackBar = new Size(220, 28);
        private readonly Size sizeComboBox = new Size(150, 20);
        private readonly Size sizeColorPicker = new Size(21, 21);
        private readonly Size sizeListBox = new Size(220, 100);
        private readonly Size sizeTimePicker = new Size(121, 21);

        public ConfigForm(T config)
        {
            InitializeComponent();
            this.config = config;
            BuildControls();
            AdjustControls();
        }

        private void BuildControls()
        {
            foreach (var f in typeof(T).GetFields())
            {
                if (IllegalType(f, out ConfigField cf)) continue;
                AddLabel(cf);
                if (cf.ControlType == ConfigType.NumberSimple) AddTrackBar(cf, f);
                else if (cf.ControlType == ConfigType.Boolean) AddRadioButton(cf, f);
                else if (cf.ControlType == ConfigType.Color) AddColorPicker(cf, f);
                else if (cf.ControlType == ConfigType.EnumSingle) AddComboBox(cf, f);
                else if (cf.ControlType == ConfigType.EnumMultiple) AddListBox(cf, f);
                else if (cf.ControlType == ConfigType.Time) AddTimePicker(cf, f);
                else AddTextBox(cf, f);
            }
        }

        private bool IllegalType(FieldInfo f, out ConfigField cf)
        {
            cf = null;
            cf = f.GetCustomAttribute(typeof(ConfigField), false) as ConfigField;
            if (cf == null) return true;
            var t = f.FieldType;
            if (cf.ControlType == ConfigType.Text) return !typeof(string).Equals(t);
            if (cf.ControlType == ConfigType.Number) return !typeof(int).Equals(t);
            if (cf.ControlType == ConfigType.FloatNumber) return !typeof(double).Equals(t);
            if (cf.ControlType == ConfigType.PathFile) return !typeof(string).Equals(t);
            if (cf.ControlType == ConfigType.PathFolder) return !typeof(string).Equals(t);
            if (cf.ControlType == ConfigType.NumberSimple) return !typeof(int).Equals(t);
            if (cf.ControlType == ConfigType.Boolean) return !typeof(bool).Equals(t);
            if (cf.ControlType == ConfigType.Color) return !typeof(Color).Equals(t);
            if (cf.ControlType == ConfigType.EnumSingle) return !typeof(int).Equals(t) && !t.IsEnum || !cf.EnumType.IsEnum;
            if (cf.ControlType == ConfigType.Time) return !typeof(DateTime).Equals(t) && !typeof(int).Equals(t) && !typeof(long).Equals(t);
            if (cf.ControlType == ConfigType.EnumMultiple)
            {
                if (!cf.EnumType.IsEnum) return true;
                if (t.IsArray && t.GetArrayRank() == 1)
                {
                    t = t.GetElementType();
                    return !typeof(int).Equals(t) && !t.IsEnum;
                }
                else
                {
                    Type gt;
                    if ((gt = GetGenericType(t)) != null) return !typeof(int).Equals(gt) && !gt.IsEnum;
                }
            }
            return true;
        }

        private void AddControl(FieldInfo field, params Control[] controls)
        {
            values.Add(new Value() { field = field, controls = controls });
            Control last = null;
            foreach (var c in controls) flowLayoutPanel1.Controls.Add(last = c);
            flowLayoutPanel1.SetFlowBreak(last, true);
        }

        private void AddLabel(ConfigField cf)
        {
            var label = new Label
            {
                Text = cf.Title,
                AutoSize = true,
                Margin = padLabel
            };
            flowLayoutPanel1.Controls.Add(label);
            if (!string.IsNullOrEmpty(cf.Description)) toolTip1.SetToolTip(label, cf.Description);
        }

        private void AddTextBox(ConfigField cf, FieldInfo field)
        {
            var tb = new TextBox
            {
                Margin = padCommon,
                Text = field.GetValue(config)?.ToString() ?? string.Empty
            };
            if (cf.ControlType == ConfigType.Number || cf.ControlType == ConfigType.FloatNumber) tb.Size = sizeSmallTextBox;
            else if (cf.ControlType == ConfigType.Text) tb.Size = sizeMediumTextBox;
            else
            {
                tb.Size = sizeLargeTextBox;
                if (cf.ControlType == ConfigType.PathFile)
                {
                    tb.DoubleClick += (s, e) =>
                    {
                        var f = new OpenFileDialog();
                        f.Filter = "所有文件|*.*";
                        if (f.ShowDialog() == DialogResult.OK)
                        {
                            var t = s as TextBox;
                            t.Clear();
                            t.AppendText(f.FileName);
                        }
                    };
                }
                else
                {
                    tb.DoubleClick += (s, e) =>
                    {
                        var f = new FolderBrowserDialog();
                        f.ShowNewFolderButton = true;
                        if (f.ShowDialog() == DialogResult.OK)
                        {
                            var t = s as TextBox;
                            t.Clear();
                            t.AppendText(f.SelectedPath);
                        }
                    };
                }
            }
            AddControl(field, tb);
        }

        private void AddTrackBar(ConfigField cf, FieldInfo field)
        {
            var tb = new TrackBar
            {
                AutoSize = false,
                Size = sizeTrackBar,
                Minimum = cf.MinInt,
                Maximum = cf.MaxInt,
                TickStyle = cf.MaxInt - cf.MinInt > 30 ? TickStyle.None : TickStyle.BottomRight
            };
            var val = (int)field.GetValue(config);
            if (val < cf.MinInt || val > cf.MaxInt) val = cf.MinInt;
            tb.Value = val;
            var label = new Label
            {
                Text = val.ToString(),
                AutoSize = true,
                Margin = padTrackBarLabel
            };
            tb.Tag = label;
            tb.ValueChanged += (s, e) =>
            {
                var t = s as TrackBar;
                (t.Tag as Label).Text = t.Value.ToString();
            };
            AddControl(field, tb, label);
        }

        private void AddRadioButton(ConfigField cf, FieldInfo field)
        {
            var rb1 = new RadioButton()
            {
                Text = cf.TrueText,
                AutoSize = true,
                Margin = padRadio,
                AutoCheck = false,
                Checked = false
            };
            var rb2 = new RadioButton()
            {
                Text = cf.FalseText,
                AutoSize = true,
                Margin = padRadio,
                AutoCheck = false,
                Checked = false
            };
            if ((bool)field.GetValue(config)) rb1.Checked = true;
            else rb2.Checked = true;
            rb1.Tag = rb2;
            rb2.Tag = rb1;
            rb1.Click += (s, e) =>
            {
                var t = s as RadioButton;
                t.Checked = true;
                (t.Tag as RadioButton).Checked = false;
            };
            rb2.Click += (s, e) =>
            {
                var t = s as RadioButton;
                t.Checked = true;
                (t.Tag as RadioButton).Checked = false;
            };
            AddControl(field, rb1, rb2);
        }

        private void AddColorPicker(ConfigField cf, FieldInfo field)
        {
            var t = new PictureBox
            {
                Margin = padCommon,
                Size = sizeColorPicker,
                BackColor = (Color)field.GetValue(config)
            };
            var anyColor = cf.AnyColor;
            var solidColor = cf.SolidColor;
            t.Click += (s, e) =>
            {
                var f = new ColorDialog();
                f.AnyColor = anyColor;
                f.SolidColorOnly = solidColor;
                if (f.ShowDialog() == DialogResult.OK) (s as PictureBox).BackColor = f.Color;
            };
            t.Cursor = Cursors.Hand;
            AddControl(field, t);
        }

        private void AddComboBox(ConfigField cf, FieldInfo field)
        {
            var cb = new ComboBox
            {
                Margin = padCommon,
                Size = sizeComboBox,
                DropDownStyle = ComboBoxStyle.DropDownList
            };
            var val = (int)field.GetValue(config);
            var k = -1;
            foreach (var v in Enum.GetValues(cf.EnumType))
            {
                if ((int)v == val) k = cb.Items.Count;
                cb.Items.Add(v);
            }
            cb.SelectedIndex = k;
            AddControl(field, cb);
        }

        private void AddListBox(ConfigField cf, FieldInfo field)
        {
            var lb = new CheckedListBox
            {
                Margin = padCommon,
                Size = sizeListBox,
                CheckOnClick = true
            };
            var val = ToSet(field.GetValue(config));
            foreach (var v in Enum.GetValues(cf.EnumType))
            {
                var i = lb.Items.Add(v);
                if (val.Contains((int)v)) lb.SetItemChecked(i, true);
            }
            AddControl(field, lb);
        }

        private void AddTimePicker(ConfigField cf, FieldInfo field)
        {
            var tp = new DateTimePicker
            {
                Margin = padCommon,
                Size = sizeTimePicker,
                Format = cf.TimeFormat,
                Value = ToDateTime(field.GetValue(config))
            };
            if (cf.TimeFormat == DateTimePickerFormat.Custom) tp.CustomFormat = cf.CustomFormat;
            AddControl(field, tp);
        }

        private void AdjustControls()
        {
            int k = 0;
            foreach (var v in values)
            {
                var x = v.controls[0].Location.X;
                if (x > k) k = x;
            }
            foreach (var v in values)
            {
                var x = v.controls[0].Location.X;
                if (x < k)
                {
                    var m = v.controls[0].Margin;
                    v.controls[0].Margin = new Padding(m.Left + k - x, m.Top, m.Right, m.Bottom);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            foreach (var v in values)
            {
                var attr = v.field.GetCustomAttributes(typeof(ConfigField), false);
                if (attr.Length == 0) continue;
                var cf = attr[0] as ConfigField;
                if (cf.ControlType == ConfigType.NumberSimple) To((v.controls[0] as TrackBar).Value, v.field);
                else if (cf.ControlType == ConfigType.Boolean) To((v.controls[0] as RadioButton).Checked, v.field);
                else if (cf.ControlType == ConfigType.Color) To((v.controls[0] as PictureBox).BackColor, v.field);
                else if (cf.ControlType == ConfigType.EnumMultiple) To((v.controls[0] as CheckedListBox).CheckedItems, v.field);
                else if (cf.ControlType == ConfigType.Time) To((v.controls[0] as DateTimePicker).Value, v.field);
                else if (cf.ControlType == ConfigType.EnumSingle)
                {
                    var cb = v.controls[0] as ComboBox;
                    if (cb.SelectedIndex == -1)
                    {
                        ShowTip(cb, "请选择一项" + cf.Title);
                        return;
                    }
                    To(cb.SelectedItem, v.field);
                }
                else
                {
                    var text = v.controls[0].Text;
                    if (cf.ControlType == ConfigType.Number)
                    {
                        if (!text.ParseInt(out int val, cf.MinInt, cf.MaxInt))
                        {
                            ShowTip(v.controls[0], $"请填写 {cf.MinInt}-{cf.MaxInt} 整数");
                            return;
                        }
                        To(val, v.field);
                    }
                    else if (cf.ControlType == ConfigType.FloatNumber)
                    {
                        if (!text.ParseDouble(out double val, cf.MinDouble, cf.MaxDouble))
                        {
                            ShowTip(v.controls[0], $"请填写 {cf.MinDouble}-{cf.MaxDouble} 小数");
                            return;
                        }
                        To(val, v.field);
                    }
                    else
                    {
                        if (cf.ControlType == ConfigType.PathFile)
                        {
                            if (!File.Exists(text))
                            {
                                ShowTip(v.controls[0], "文件不存在");
                                return;
                            }
                        }
                        else if (cf.ControlType == ConfigType.PathFolder)
                        {
                            if (!Directory.Exists(text))
                            {
                                ShowTip(v.controls[0], "文件夹不存在");
                                return;
                            }
                        }
                        To(text, v.field);
                    }
                }
            }
            DialogResult = DialogResult.OK;
            Close();
        }

        private void ShowTip(Control control, string text)
        {
            flowLayoutPanel1.ScrollControlIntoView(control);
            toolTip1.Show(text, control, 0, -control.Height, 2000);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Close();
        }

        private HashSet<int> ToSet(object val)
        {
            var set = new HashSet<int>();
            if (val == null) return set;
            var t = val.GetType();
            if (t.IsArray)
            {
                var array = val as Array;
                for (var i = 0; i < array.Length; i++) set.Add((int)array.GetValue(i));
            }
            else if (val is IEnumerable)
            {
                foreach (var item in (val as IEnumerable)) set.Add((int)item);
            }
            return set;
        }

        private DateTime ToDateTime(object val)
        {
            var t = val?.GetType();
            if (typeof(int).Equals(t)) return ((int)val).ToDateTime();
            if (typeof(long).Equals(t)) return ((long)val).ToDateTime();
            return (DateTime)val;
        }

        private Type GetGenericType(Type type)
        {
            if (type == null) return null;
            Type col;
            foreach (var t in type.GetInterfaces())
            {
                if (t.IsGenericType && typeof(ICollection<>).Equals(t.GetGenericTypeDefinition()))
                    return t.GenericTypeArguments[0];
                if ((col = GetGenericType(t)) != null) return col;
            }
            return GetGenericType(type.BaseType);
        }

        private void To(object val, FieldInfo field)
        {
            if (val == null || field == null)
            {
                field.SetValue(Result, null);
                return;
            }
            var dstType = field.FieldType;
            var srcType = val.GetType();
            if (srcType.Equals(dstType)) field.SetValue(Result, val);
            else if (srcType.IsEnum) field.SetValue(Result, val);
            else if (typeof(DateTime).Equals(srcType))
            {
                if (typeof(int).Equals(dstType)) field.SetValue(Result, ((DateTime)val).ToIntUnix());
                else field.SetValue(Result, ((DateTime)val).ToLongUnix());
            }
            else if (dstType.IsArray)
            {
                var list = val as ICollection;
                var array = Array.CreateInstance(dstType.GetElementType(), list.Count);
                var i = 0;
                if (typeof(int).Equals(dstType.GetElementType()))
                {
                    foreach (var item in list) array.SetValue((int)item, i++);
                }
                else
                {
                    foreach (var item in list) array.SetValue(item, i++);
                }
                field.SetValue(Result, array);
            }
            else
            {
                Type gt;
                if ((gt = GetGenericType(dstType)) != null)
                {
                    var collection = Activator.CreateInstance(dstType);
                    var method = dstType.GetMethod("Add", new Type[] { gt });
                    if (typeof(int).Equals(gt))
                    {
                        foreach (var item in (val as ICollection)) method.Invoke(collection, new object[] { (int)item });
                    }
                    else
                    {
                        foreach (var item in (val as ICollection)) method.Invoke(collection, new object[] { item });
                    }
                    field.SetValue(Result, collection);
                }
            }
        }
    }
}
